/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar.yins;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Comparator;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import com.lucky_byte.pay.jar.Encryptor;
import com.lucky_byte.pay.jar.JdbcRecord;
import com.lucky_byte.pay.jar.JdbcTable;
import com.lucky_byte.pay.jar.Runtime;

/**
 * 银联 ACP 功能模块
 */
public class LibYins
{
	private static final Logger logger = LogManager.getLogger();

	/**
	 * 计算报文签名
	 *
	 * @param packet PacketY30 报文
	 * @param key_name 签名密钥名称
	 *
	 * @return 签名字符串
	 */
	public static boolean sign(PacketY30 packet, String key_name) {
		byte[] sign_data = getSignData(packet);
		if (sign_data == null) {
			return false;
		}
		Encryptor encryptor = Encryptor.getInstance();
		byte[] signature = encryptor.signSHA1WithRSA(key_name, sign_data);
		if (signature == null) {
			return false;
		}
		packet.set("sign", new String(Base64.encode(signature)));
		packet.set("sign_type", "RSA");
		return true;
	}

	/**
	 * 计算 PacketY20 报文签名
	 */
	public static boolean sign(PacketY20 packet, String key_name) {
		byte[] sign_data = packet.getMsgXMLBytes();
		if (sign_data == null) {
			logger.error("获取签名数据失败，签名失败.");
			return false;
		}
		Encryptor encryptor = Encryptor.getInstance();
		byte[] signature = encryptor.signMD5WithRSA(key_name, sign_data);
		if (signature == null) {
			return false;
		}
		packet.setHeadCheck(Base64.toBase64String(signature));
		return true;
	}

	/**
	 * 验证报文的签名，当前软件加密机支持 SHA1withRSA 算法
	 * 系统提供 DSA 算法的支持，但是没有整合，因为规范中未提及其它算法.
	 *
	 * @return 成功或失败
	 */
	public static boolean verifySign(PacketY30 packet, String key_name) {
		Encryptor encryptor = Encryptor.getInstance();
		if (encryptor == null) {
			return false;
		}
		byte[] sign_data = getSignData(packet);
		if (sign_data == null) {
			return false;
		}
		String signature = packet.get("sign");
		if (signature == null) {
			logger.warn("报文缺少[sign]字段，无法验证签名.");
			return false;
		}
		String sign_type = packet.get("sign_type");
		if (sign_type == null) {
			logger.warn("报文缺少[sign_type]字段，无法验证签名.");
			return false;
		}
		if (sign_type.equalsIgnoreCase("RSA")) {
			return encryptor.verifySignSHA1WithRSAByAlias(key_name, sign_data,
					Base64.decode(signature));
		}
		logger.error("系统不支持签名方法[{}]，验证签名失败.", sign_type);
		return false;
	}

	/**
	 * 验证报文签名
	 *
	 * @param packet PacketY20 报文
	 * @param key_name 证签证书
	 * @return 成功或失败
	 */
	public static boolean verifySign(PacketY20 packet, String key_name) {
		String orig_msg_data = packet.getOrigMsgData();
		if (orig_msg_data == null) {
			logger.error("报文缺少原始msg数据，验证签名失败.");
			return false;
		}
		String signature = packet.getHeadCheck();
		if (signature == null) {
			logger.error("报文缺少签名数据，验证签名失败.");
			return false;
		}
		Encryptor encryptor = Encryptor.getInstance();
		return encryptor.verifySignMD5WithRSAByAlias(key_name,
				Base64.decode(orig_msg_data), Base64.decode(signature));
	}

	/**
	 * 按照字母顺序输出 key=value&key=value... 这样的字符串，其中
	 * key 是报文中的字段名称，value 是字段的值.
	 * <p>
	 * 这个函数在处理时会自动忽略 'signature' 字段.
	 * </p>
	 */
	private static byte[] getSignData(PacketY30 packet) {
		List<String> keyset = packet.keySet();
		keyset.sort(new Comparator<String>() {
			@Override
			public int compare(String s1, String s2) {
				return s1.compareTo(s2);
			}
		});
		StringBuilder builder = new StringBuilder();
		for (String key : keyset) {
			if (key.equalsIgnoreCase("sign")) {
				continue;
			}
			String value = packet.get(key);
			if (value.isEmpty()) {
				continue;
			}
			builder.append(key);
			builder.append("=");
			builder.append(value);
			builder.append("&");
		}
		String sign_data = builder.substring(0, builder.length() - 1);
		logger.trace("待签名数据[{}].", sign_data);

		String encoding = packet.get("charset");
		if (encoding == null) {
			encoding = "UTF-8";
		}
		try {
			return sign_data.getBytes(encoding);
		} catch (UnsupportedEncodingException e) {
			logger.error("报文编码[{}]无效.", encoding);
			return null;
		}
	}

	/**
	 * 将商户的签名证书添加到加密机
	 * 仅用于 LuckPay APP 项目
	 */
	public static boolean loadMerCerts(JdbcRecord mer_record) {
		Encryptor encryptor = Encryptor.getInstance();

		String mer_uuid = mer_record.getString("uuid");
		String alias = mer_uuid + "-sign";

		// 如果加密机中不存在此证书，则加载到加密机中，加载到加密机中后可以提高后续处理速度
		if (!encryptor.contain(alias)) {
			File sign_cert = Runtime.fileFor(
					"/mer/" + mer_uuid + "/certs/sign.pfx");
			if (!sign_cert.exists()) {
				logger.error("商户[{}]的签名证书[{}]不存在.", mer_uuid,
						sign_cert.getAbsolutePath());
				return false;
			}
			String sign_key = mer_record.getString("sign_key");
			if (sign_key == null || sign_key.trim().isEmpty()) {
				logger.error("商户[{}]没有配置签名证书密钥.", mer_uuid);
				return false;
			}
			// 添加到加密机
			if (!encryptor.addPKCS12Cert(sign_cert, sign_key.trim(), alias)) {
				logger.error("添加商户[{}]的签名证书失败.", mer_uuid);
				return false;
			}
			logger.info("添加商户[{}]的签名证书成功.", mer_uuid);
		}
		alias = mer_uuid + "-verify";
		if (!encryptor.contain(alias)) {
			File verify_sign_cert = Runtime.fileFor(
					"/mer/" + mer_uuid + "/certs/verify_sign.cer");
			if (!verify_sign_cert.exists()) {
				logger.error("商户[{}]的验签证书[{}]不存在.", mer_uuid,
						verify_sign_cert.getAbsolutePath());
				return false;
			}
			// 添加到加密机
			if (!encryptor.addX509Cert(verify_sign_cert, alias)) {
				logger.error("添加商户[{}]的验签证书失败.", mer_uuid);
				return false;
			}
			logger.info("添加商户[{}]的验签证书成功.", mer_uuid);
		}
		return true;
	}

	/**
	 * 通过商户 uuid 加载其证书
	 * 仅用于 LuckPay APP 项目
	 */
	public static boolean loadMerCerts(String mer_uuid) {
		JdbcTable table = JdbcTable.listBy1_NE(
				"web_users", "uuid = ?", mer_uuid);
		if (table == null || table.getRecordCount() == 0) {
			return false;
		}
		return loadMerCerts(table.getRecord(0));
	}

	/**
	 * 清除商户的证书
	 * 仅用于 LuckPay APP 项目
	 *
	 * @param mer_uuid 商户 UUID
	 */
	public static void clearMerCerts(String mer_uuid) {
		Encryptor encryptor = Encryptor.getInstance();

		encryptor.deleteByAlias(mer_uuid + "-sign");
		encryptor.deleteByAlias(mer_uuid + "-verify");
	}

}
